package com.tianji.learning.service.impl;

import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.BooleanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.DateUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.constants.RedisConstants;
import com.tianji.learning.domain.po.SignRecord;
import com.tianji.learning.domain.vo.SignResultVO;
import com.tianji.learning.mapper.SignRecordMapper;
import com.tianji.learning.mq.message.SignInMessage;
import com.tianji.learning.service.ISignRecordService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.List;

/**
 * <p>
 * 签到记录表 服务实现类
 * </p>
 *
 * @author huanghui
 * @since 2023-07-29
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class SignRecordServiceImpl extends ServiceImpl<SignRecordMapper, SignRecord> implements ISignRecordService {

    private final StringRedisTemplate redisTemplate;

    private final RabbitMqHelper mqHelper;

    @Override
    public SignResultVO addSignRecords() {
        //1.签到
        //1.1获取登录用户
        Long userId = UserContext.getUser();
        //1.2获取日期
        LocalDate now = LocalDate.now();
        //1.3.拼接key
        String key=RedisConstants.SIGN_RECORD_KEY_PREFIX+userId+now.format(DateUtils.SIGN_DATE_SUFFIX_FORMATTER);
        //1.4计算now值
        int offset=now.getDayOfMonth()-1;
        //1.5保存签到信息
        Boolean exists = redisTemplate.opsForValue().setBit(key, offset, true);
        if (BooleanUtils.isTrue(exists)) {
            throw new BizIllegalException("不允许重复签到");
        }
        //2.开始计算连续天数
        int signDays=countSignDays(key,now.getDayOfMonth());
        //  3.计算签到得分
        int rewardPoints=0;
        switch (signDays){
            case 7:
                rewardPoints=10;
                break;
            case 14:
                rewardPoints=20;
                break;
            case 28:
                rewardPoints=40;
                break;
        }
        //  4.保存积分明细记录
        mqHelper.send(
                MqConstants.Exchange.LEARNING_EXCHANGE,
                MqConstants.Key.SIGN_IN,
                SignInMessage.of(userId,rewardPoints+1)
        );
        //5.封装放回
        SignResultVO vo = new SignResultVO();
        vo.setSignDays(signDays);
        vo.setRewardPoints(rewardPoints);
        return vo;
    }

    @Override
    public Byte[] querySignRecords() {

        //1.获取当前登录用户
        Long userId = UserContext.getUser();
        //2.获取日期
        LocalDate now = LocalDate.now();
        int dayOfMonth = now.getDayOfMonth();
        //3.拼接key
        String key=RedisConstants.SIGN_RECORD_KEY_PREFIX+userId+now.format(DateUtils.SIGN_DATE_SUFFIX_FORMATTER);
        //4.读取
        List<Long>  result= redisTemplate.opsForValue()
                .bitField(key, BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));
        if (CollUtils.isEmpty(result)) {
            return new Byte[0];

        }

        int num = result.get(0).intValue();
        Byte[] arrByte = new Byte[dayOfMonth];
        int pos=dayOfMonth-1;
        while (pos>=0){
            arrByte[pos--]=(byte)(num&1) ;
            num>>>=1;
        }
        return arrByte;
    }

    private int countSignDays(String key, int len) {
        List<Long> result = redisTemplate.opsForValue()
                .bitField(key, BitFieldSubCommands.create()
                        .get(BitFieldSubCommands.BitFieldType.unsigned(len))
                        .valueAt(0));
        if (CollUtils.isEmpty(result)) {
            return 0;
        }
        int num =result.get(0).intValue();
        //定义一个计数器
        /*
        统计连续点赞的天数
        31
        00011111
        采用后开始的运算截取的运算规则
        进行& 1 运算 等于1 则代表是签到的，如果遇到0则断签，
        >>> 无符号 除以2^1

         */
        int count=0;
        while ((num & 1)==1){
            count++;
            num>>>=1;
        }
        return count;
    }


}
